package eval

import (
	"showen/lex"
	"showen/obj"
	"showen/parse"
	"testing"
)

func TestRationalize(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			"rationalize zero tolerance",
			"(rationalize 0 0)",
			"0",
		},
		{
			"rationalize simple case",
			"(rationalize 1/3 1/10)",
			"1/3",
		},
		{
			"rationalize with integer",
			"(rationalize 1 1/10)",
			"1",
		},
		{
			"rationalize decimal with tolerance",
			"(rationalize 0.3 0.1)",
			"1/3",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("ParseExpression() error = %v", err)
			}

			result, err := EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			if result.Inspect() != tt.expected {
				t.Errorf("EvalExpression() = %v, want %v", result.Inspect(), tt.expected)
			}
		})
	}
}

func TestRationalizeIntegration(t *testing.T) {
	tests := []struct {
		input string
		want  string
	}{
		{
			"(rationalize 0.3 0.1)",
			"1/3",
		},
		{
			"(rationalize 0 1)",
			"0",
		},
		{
			"(rationalize 3 1)",
			"3",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("ParseExpression() error = %v", err)
			}

			result, err := EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			if result.Inspect() != tt.want {
				t.Errorf("EvalExpression() = %v, want %v", result.Inspect(), tt.want)
			}
		})
	}
}

func TestExpLog(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			"exp of 0",
			"(exp 0)",
			"1.0",
		},
		{
			"exp of 1",
			"(exp 1)",
			"2.718281828459045",
		},
		{
			"exp of integer",
			"(exp 2)",
			"7.38905609893065",
		},
		{
			"exp of rational",
			"(exp 1/2)",
			"1.6487212707001282",
		},
		{
			"log of 1",
			"(log 1)",
			"0.0",
		},
		{
			"log of e",
			"(log 2.718281828459045)",
			"1.0",
		},
		{
			"log of rational",
			"(log 2/3)",
			"-0.40546510810816444",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("ParseExpression() error = %v", err)
			}

			result, err := EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			if result.Inspect() != tt.expected {
				t.Errorf("EvalExpression() = %v, want %v", result.Inspect(), tt.expected)
			}
		})
	}
}

func TestAsinAcosAtan(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			"asin of 0",
			"(asin 0)",
			"0.0",
		},
		{
			"asin of 1",
			"(asin 1)",
			"1.5707963267948966",
		},
		{
			"acos of 0",
			"(acos 0)",
			"1.5707963267948966",
		},
		{
			"acos of 1",
			"(acos 1)",
			"0.0",
		},
		{
			"atan of 0",
			"(atan 0)",
			"0.0",
		},
		{
			"atan of 1",
			"(atan 1)",
			"0.7853981633974483",
		},
		{
			"atan with two arguments (1, 1)",
			"(atan 1 1)",
			"0.7853981633974483",
		},
		{
			"atan with two arguments (1, 0)",
			"(atan 1 0)",
			"1.5707963267948966",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("ParseExpression() error = %v", err)
			}

			result, err := EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			if result.Inspect() != tt.expected {
				t.Errorf("EvalExpression() = %v, want %v", result.Inspect(), tt.expected)
			}
		})
	}
}

func TestSqrt(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			"sqrt of perfect square integer",
			"(sqrt 16)",
			"4",
		},
		{
			"sqrt of non-perfect square integer",
			"(sqrt 2)",
			"1.4142135623730951",
		},
		{
			"sqrt of rational",
			"(sqrt 4/9)",
			"0.6666666666666666",
		},
		{
			"sqrt of real",
			"(sqrt 2.0)",
			"1.4142135623730951",
		},
		{
			"sqrt of negative number (complex result)",
			"(sqrt -4)",
			"0.0+2.0i",
		},
		{
			"sqrt of zero",
			"(sqrt 0)",
			"0",
		},
		{
			"sqrt of one",
			"(sqrt 1)",
			"1",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("ParseExpression() error = %v", err)
			}

			result, err := EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			if result.Inspect() != tt.expected {
				t.Errorf("Expected %s, got %s", tt.expected, result.Inspect())
			}
		})
	}
}

func TestSqrtIntegration(t *testing.T) {
	tests := []struct {
		input string
		want  string
	}{
		{
			"(sqrt 9)",
			"3",
		},
		{
			"(sqrt 0)",
			"0",
		},
		{
			"(sqrt 1)",
			"1",
		},
		{
			"(define a 16) (sqrt a)",
			"4",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			program, err := parser.ParseProgram()
			if err != nil {
				t.Fatalf("parser.ParseProgram() error = %v", err)
			}

			result, err := EvalProgram(program, env)
			if err != nil {
				t.Fatalf("EvalProgram() error = %v", err)
			}

			if result.Inspect() != tt.want {
				t.Errorf("EvalProgram() = %v, want %v", result.Inspect(), tt.want)
			}
		})
	}
}

func TestExpt(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			"integer power of integer",
			"(expt 2 3)",
			"8",
		},
		{
			"zero power",
			"(expt 5 0)",
			"1",
		},
		{
			"negative integer power",
			"(expt 2 -3)",
			"0.125",
		},
		{
			"fractional power",
			"(expt 4 1/2)",
			"2",
		},
		{
			"real base with integer exponent",
			"(expt 2.5 2)",
			"6.25",
		},
		{
			"negative base with integer exponent",
			"(expt -2 3)",
			"-8",
		},
		{
			"negative base with even exponent",
			"(expt -2 2)",
			"4",
		},
		{
			"zero to positive power",
			"(expt 0 3)",
			"0",
		},
		{
			"one to any power",
			"(expt 1 100)",
			"1",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("ParseExpression() error = %v", err)
			}

			result, err := EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			if result.Inspect() != tt.expected {
				t.Errorf("Expected %s, got %s", tt.expected, result.Inspect())
			}
		})
	}
}

func TestExptIntegration(t *testing.T) {
	tests := []struct {
		input string
		want  string
	}{
		{
			"(expt 2 10)",
			"1024",
		},
		{
			"(expt 5 0)",
			"1",
		},
		{
			"(expt 3 -1)",
			"0.3333333333333333",
		},
		{
			"(define x 2) (define y 3) (expt x y)",
			"8",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			program, err := parser.ParseProgram()
			if err != nil {
				t.Fatalf("parser.ParseProgram() error = %v", err)
			}

			result, err := EvalProgram(program, env)
			if err != nil {
				t.Fatalf("EvalProgram() error = %v", err)
			}

			if result.Inspect() != tt.want {
				t.Errorf("EvalProgram() = %v, want %v", result.Inspect(), tt.want)
			}
		})
	}
}

func TestComplexNumberFunctions(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			"make-rectangular",
			"(make-rectangular 1 2)",
			"1+2i",
		},
		{
			"make-polar",
			"(make-polar 1 0)",
			"1.0",
		},
		{
			"real-part of complex",
			"(real-part (make-rectangular 1 2))",
			"1",
		},
		{
			"imag-part of complex",
			"(imag-part (make-rectangular 1 2))",
			"2",
		},
		{
			"real-part of real",
			"(real-part 5)",
			"5",
		},
		{
			"imag-part of real",
			"(imag-part 5)",
			"0",
		},
		{
			"sqrt of negative number",
			"(sqrt -4)",
			"0.0+2.0i",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("ParseExpression() error = %v", err)
			}

			result, err := EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			if result.Inspect() != tt.expected {
				t.Errorf("EvalExpression() = %v, want %v", result.Inspect(), tt.expected)
			}
		})
	}
}

func TestMagnitudeAngle(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			"magnitude of positive integer",
			"(magnitude 5)",
			"5",
		},
		{
			"magnitude of negative integer",
			"(magnitude -5)",
			"5",
		},
		{
			"magnitude of positive rational",
			"(magnitude 3/4)",
			"3/4",
		},
		{
			"magnitude of negative rational",
			"(magnitude -3/4)",
			"3/4",
		},
		{
			"magnitude of complex number",
			"(magnitude (make-rectangular 3 4))",
			"5.0",
		},
		{
			"angle of positive integer",
			"(angle 5)",
			"0",
		},
		{
			"angle of negative integer",
			"(angle -5)",
			"3.141592653589793",
		},
		{
			"angle of positive rational",
			"(angle 3/4)",
			"0",
		},
		{
			"angle of negative rational",
			"(angle -3/4)",
			"3.141592653589793",
		},
		{
			"angle of complex number",
			"(angle (make-rectangular 1 1))",
			"0.7853981633974483",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("ParseExpression() error = %v", err)
			}

			result, err := EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			if result.Inspect() != tt.expected {
				t.Errorf("EvalExpression() = %v, want %v", result.Inspect(), tt.expected)
			}
		})
	}
}

func TestExactInexactConversions(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			"exact->inexact integer",
			"(exact->inexact 5)",
			"5.0",
		},
		{
			"exact->inexact rational",
			"(exact->inexact 1/2)",
			"0.5",
		},
		{
			"inexact->exact integer",
			"(inexact->exact 5.0)",
			"5/1",
		},
		{
			"inexact->exact rational",
			"(inexact->exact 0.5)",
			"1/2",
		},
		{
			"inexact->exact complex",
			"(inexact->exact (make-rectangular 1.0 2.0))",
			"1/1+2/1i",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("ParseExpression() error = %v", err)
			}

			result, err := EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			if result.Inspect() != tt.expected {
				t.Errorf("EvalExpression() = %v, want %v", result.Inspect(), tt.expected)
			}
		})
	}
}
